package com.GPS_based_training.progetto_so2_2014_2015;


import java.text.DecimalFormat;
import java.util.Calendar;

import org.json.JSONException;

import com.GPS_based_training.JSONUtils.JSONStandardization;
import com.GPS_based_training.custom_components.CompositeStats;
import com.GPS_based_training.custom_components.SpeedArrow;
import com.GPS_based_training.custom_components.TrainingButton;
import com.GPS_based_training.custom_components.TrainingChronometer;
import com.GPS_based_training.database.DatabaseAdapter;
import com.GPS_based_training.progetto_so2_2014_2015.R;
import com.GPS_based_training.service.LocationLoggerService;
import com.GPS_based_training.service.LocationLoggerService.LocalBinder;
import com.GPS_based_training.service.LocationLoggerService.OnNewGPSPointsListener;
import com.GPS_based_training.support.Training;
import com.GPS_based_training.support.TrainingElement;

import android.app.Activity;
import android.app.Dialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.MenuInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.os.IBinder;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

/**
 * This class represents the activity that manages the training
 * @author Andreas Scalas e Danilo Dessi'
 *
 */
public class MainActivity extends Activity implements OnClickListener{
	
	
	/******* GRAPHIC OBJECTS *******/
	private Dialog dialogNameTraining;	//Dialog to set up name of training
	private Dialog dialogSetLastXMeters;//Dialog to set up last x meters of training
	private TrainingButton start;
	private CompositeStats stats;
	private SpeedArrow arrow;
	private TrainingChronometer chronometer;//It is used to link the chronometer
	
	
	/******* CONSTANTS *******/
	private final double MULTIPLIER_FOR_KILOMETERS_HOURS = 3.6; // A multiplier to convert m/s in km/h
	private final double MILLISECOND_TO_SECOND = 1000; // A multiplier to convert s in ms
	private final String DEFAULT_LAST_X_METERS = "100"; //The default last x meters
	private final String INDEX_LAST_X_METERS = "LAST_X_METERS";//Constant to retrieve last x meters from  preferences
	private final double DIVISOR_FROM_METERS_TO_KILOMETERS = 1000;
	private static final int MAX_ALPHA_INTENSITY = 255;
	protected static final double ACCEPTANCE_RATIO = 1.0F/20.0F;
	private final String VALUE_SPEED = "VALUE_SPEED"; //Constant used to receive values
	private final String PREFERENCES_MAIN_ACTIVITY = "Preferences_main_activity";//Constant to access shared preferences
	
	
	/******* ATTRIBUTES OF THE STATE OF THE ACTIVITY *******/
	private Training training;	//training is an object that is used to preserve the information of the actual session
	private LocationLoggerService mService;// The interface we will call on the service
	private boolean flagServiceGPS;//flagServiceGPS is a flag that indicates the status of link with service
	private Intent serviceGPSIntent;//Intent associated to the GPS service 
	private double setSpeed; //it used to indicate the speed that the user chooses; actually is unused!
	private double setLastXMeters; //it is used to compute statistics on last x meters
	private boolean stateChronometer;//it used to save the state of chronometer and to manage the updates
	private boolean stateGPS;//it is used to save the connection of GPS with sattl
	
	
	/******* OBJECTS TO CONNECT TO OTHER COMPONENTS OF THE APPLICATIONS *******/
	private DatabaseAdapter dba;//It is used to bind to database
	private ServiceConnection mConnection;//It is used to bind with the service
	private OnNewGPSPointsListener clientListener;//It is used to listen service updates
	
	
	/****** THREAD TO MANAGE BLOCK FUNCTIONS *******/
	private AsyncTask<Void, Void, Void> threadToManageGPSUpdate;
	 
	
	/******* LIFE-CYCLE METHODS OF THE ACTIVITY *******/
	@Override
	/**
	 * @author Andreas Scalas & Danilo Dessi'
	 * This method creates the activity
	 */
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        initializzationStateAndGraphic();
        
       //Linking to service
        bindService(serviceGPSIntent, mConnection, Context.BIND_AUTO_CREATE);
             
    }
	
	
	@Override
	/**
	 * @author Danilo Dessi'
	 * This method deletes the activity
	 */
	protected void onDestroy(){
		
		super.onDestroy();
		
		//De-registration listener
		mService.removeOnNewGPSPointsListener();
		
		stateGPS = false;
				
		// Disconnection from service
		if(flagServiceGPS) {
			
			// Disconnecting connection
			unbindService(mConnection);
			flagServiceGPS = false;
			
		}
		
		//we delete a thread that manages the updates
		threadToManageGPSUpdate.cancel(true);
		
		
	}
	
	
	/******* METHODS TO MANAGE EVENTS OF THE VIEW *******/

	@Override
	/**
	 * @author Andreas Scalas
	 * Method that allow to manage the response to the onClick event
	 */
    public void onClick(View view) {
            
		//Obtains graphic objects
		TrainingButton start  = (TrainingButton) this.findViewById(R.id.start_button);
		TrainingChronometer chronometer = (TrainingChronometer) this.findViewById(R.id.chronometer);
		TrainingButton stop = (TrainingButton) this.findViewById(R.id.stop_button);
		SpeedArrow arrow = (SpeedArrow) this.findViewById(R.id.speed_arrow);
		
		
		if( view.getId() == R.id.start_button ){
			
			//If the button is the start/pause button it can be pressed only if the GPS is available
			
			if(stateGPS) {
			
				if(start.getText().equals("START")){
					
					//If start has been clicked, the button has to become pause.
					chronometer.start();
					stop.setVisibility(View.GONE);
					arrow.setVisibility(View.VISIBLE);
					stateChronometer = true;
					
				}else{
					
					/*If pause has been clicked, the button has to become pause.
					 *and the stop button has to become visible and clickable.
					 */
					chronometer.pause();
					arrow.setVisibility(View.GONE);
					stop.setVisibility(View.VISIBLE);
					stop.setOnClickListener(this);	
					stateChronometer = false;
				
				}
			
				start.invalidate();
				start.changeButtonType();
			
			} else {
				
				Toast.makeText(this, getString(R.string.wait_GPS_connection), Toast.LENGTH_SHORT).show();
				
			}
			
		} else {

			if(!chronometer.isRunning()){
				
				//If stop is clicked, it has to become invisible, the chronometer has to stop and the training has to be saved.
				
				saveInDatabase();
				chronometer.stop();
				chronometer.invalidate();
				start.setSpeed("");
				start.invalidate();
				arrow.setTooFast(false);
				arrow.setTooSlow(false);
				arrow.invalidate();
				stop.setVisibility(View.GONE);
				arrow.setVisibility(View.VISIBLE);
 

			}
			
		}
		
            
    }
	
	/******* METHODS TO MANAGE MENUS *******/
	
	@Override
	/**
	 * @author Danilo Dessi'
	 * This method creates the options menu
	 */
	public boolean onCreateOptionsMenu(Menu menu) {
	    MenuInflater inflater = getMenuInflater();
	    inflater.inflate(R.menu.main_menu, menu);
	    return true;
	}
	
	@Override
	/**
	 * @author Danilo Dessi'
	 * This method associates the menu items with the actions 
	 */
	public boolean onOptionsItemSelected(MenuItem item) {
		
		switch(item.getItemId()) {
		
		case R.id.settings:
			setLastXMeter();
			break;
		
		}
		
		return super.onOptionsItemSelected(item);
	}
	
	
	/******* METHODS THAT ARE TO SUPPORT OF THE ACTIVITY *******/
	/**
	 * @author Andreas Scalas e Danilo Dessi'
	 * 
	 * This method initializes graphic and state variales
	 */
	private void initializzationStateAndGraphic(){
		
		//Retrieving of the set values
        Bundle extras = getIntent().getExtras();
        setSpeed = extras.getDouble(VALUE_SPEED);
		
        //link to view objects
		chronometer = (TrainingChronometer) this.findViewById(R.id.chronometer);
        start = (TrainingButton) this.findViewById(R.id.start_button);
        stats = (CompositeStats) this.findViewById(R.id.compositeStats);
        arrow = (SpeedArrow) this.findViewById(R.id.speed_arrow);
        start.setOnClickListener(this);
		
        stats.setText1("0.0 " + getString(R.string.measurement_unit_distance));
      	stats.setText2("0.0 " + getString(R.string.measurement_unit_speed));
      	stats.setText3("0.0 " + getString(R.string.measurement_unit_pace));
        
        //Initialization intent service
        serviceGPSIntent = new Intent(this, LocationLoggerService.class);
        
        //It is used to manage the database
        dba = new DatabaseAdapter(getApplicationContext());
        
        //Creating object that saves data of this session of training the name is setted when it is saved in database       
        training = new Training();
        
        
        
        //It is used to connect to service for retrieving GPS values
        mConnection = new ServiceConnection() {
    		
    		@Override
    		public void onServiceConnected(ComponentName className, IBinder service) {
    						
    			LocalBinder binder = (LocalBinder) service;
    			mService = binder.getService();
    			flagServiceGPS = true;
    			
    			//we create a thread to manage updates
    			threadToManageGPSUpdate = managerGPSUpdate();
    			
    			//we launch the thread
    	        threadToManageGPSUpdate.execute(null,null,null);    			
    		
    		}
    		
    		@Override
    		public void onServiceDisconnected(ComponentName className) {
    			
    			// This is called when the connection with the service has been
    			// unexpectedly disconnected
    			flagServiceGPS = false;
    			
    		}
    	
    	};

    	
    	//we retrieve the value of last x meters that is saved in the preferences
    	SharedPreferences prefs = getSharedPreferences(PREFERENCES_MAIN_ACTIVITY, Context.MODE_PRIVATE);
        setLastXMeters = Double.parseDouble(prefs.getString(INDEX_LAST_X_METERS, DEFAULT_LAST_X_METERS));
        
        stateChronometer = false;
        stateGPS = false;
        
	}
	
	
	/**
	 * @author Danilo Dessi'
	 * 
	 * This method uses a dialog to allows to user to set up the last x meters to compute statistics
	 */
	private void setLastXMeter() {
		//we create a dialog and set up layout and title
		dialogSetLastXMeters = new Dialog(this);
		dialogSetLastXMeters.setContentView(R.layout.dialog_set_last_x_meters);
		dialogSetLastXMeters.setTitle(getString(R.string.title_dialog_set_x_meters));
		
		//object to retrieve and save the value of last x meters that is inserted by user
		final SharedPreferences prefs = getSharedPreferences(PREFERENCES_MAIN_ACTIVITY, Context.MODE_PRIVATE);
		
		//we link to graphics element of the dialog window
		final EditText etSetLastXMeters = (EditText) dialogSetLastXMeters.findViewById(R.id.et_set_last_x_meters);
		final Button btnSetLastXMeters = (Button) dialogSetLastXMeters.findViewById(R.id.btn_set_last_x_meters);
		
		//we update the view of dialog with the previous value of last x meters that was saved
		String valueLastXMeters = prefs.getString(INDEX_LAST_X_METERS, DEFAULT_LAST_X_METERS);
		etSetLastXMeters.setText(valueLastXMeters);
		
		// we add a event manager to button of the dialog
		btnSetLastXMeters.setOnClickListener(new OnClickListener(){

			@Override
			public void onClick(View arg0) {
				//retrieval value from edit text
				String stringValueXMeters = etSetLastXMeters.getText().toString();
				
				if( stringValueXMeters.compareTo("") == 0) {
					Toast.makeText(arg0.getContext(), getString(R.string.distance_is_not_inserted), Toast.LENGTH_SHORT).show();					
				} else {
					
					//we convert the value that is insert into double variable
					double tempSetLastXMeters = Double.parseDouble(stringValueXMeters);
					
					if( tempSetLastXMeters > 0 ) {
						
						//we convert the value in double and save it in attribute of the activity
						setLastXMeters = Double.parseDouble(stringValueXMeters);
						
						//we save the last x meters in the preferences
						SharedPreferences.Editor editor = prefs.edit();
						editor.putString(INDEX_LAST_X_METERS, setLastXMeters + "");
						editor.commit();
						
						Toast.makeText(arg0.getContext(), getString(R.string.setted) + " " + stringValueXMeters + " "+ getString(R.string.meters) , Toast.LENGTH_SHORT).show();
						
						//close dialog
						dialogSetLastXMeters.dismiss();
						
					} else {
						Toast.makeText(arg0.getContext(), getString(R.string.meters_is_zero) , Toast.LENGTH_SHORT).show();
					}
					
				}
			}
			
		});
		
		//show dialog
		dialogSetLastXMeters.show();
		
	}
	
	
	/**
	 * @author Danilo Dessi'
	 * 
	 * This method is used to open a dialog interface and to request the name of the training to user.
	 * Subsequently it saves the training in database
	 */
	private void saveInDatabase() {
		final EditText etName;			//Name that the user inserts
		final Button btnSave;			// Button to save training
		final Button btnCancel;			// Button to close the dialog
		
		//Management by dialog to set up the name of training
		dialogNameTraining = new Dialog(this);
        dialogNameTraining.setContentView(R.layout.dialog_save_training);
        dialogNameTraining.setTitle(getString(R.string.title_dialog_saving));
        dialogNameTraining.setCancelable(false);
        
        //Retrieval linkers to view elements
        etName = (EditText) dialogNameTraining.findViewById(R.id.et_name_training);
		btnSave = (Button) dialogNameTraining.findViewById(R.id.btn_save_training);
		btnCancel = (Button) dialogNameTraining.findViewById(R.id.btn_cancel_saving_training);
		
		
		btnSave.setOnClickListener(new OnClickListener(){

			@Override
			public void onClick(View arg0) {
				//Retrieval name from edit text
				String s = etName.getText().toString();
				
				if(s.compareTo("") == 0) {
					Toast.makeText(arg0.getContext(), getString(R.string.name_is_not_valid), Toast.LENGTH_SHORT).show();
				}
				else {
					
					//we set up the date and hour of the training
					training.setDate(getDate()); 
					
					//we set up the name of the training
					training.setName(s);
					
					//we set up the json object of the training
					JSONStandardization json = null;
					try {
						
						json = new JSONStandardization(training);
						
					} catch (JSONException e) {
						
						e.printStackTrace();
						
					}
					
					//We retention training in database
					dba.open();
					dba.createTraining( json.getStandardObject());
					dba.close();
					Toast.makeText(arg0.getContext(), training.getName() + " " + getString(R.string.saved_on_device) ,Toast.LENGTH_SHORT).show();
					
					//close dialog
					dialogNameTraining.dismiss();
										
				}
				
			}
			
		});
		
		btnCancel.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View arg0) {
				//close dialog
				dialogNameTraining.dismiss();				
			}
		});
		
		dialogNameTraining.show();
		
	}
	
	
	/**
	 * @author Danilo Dessi'
	 * 
	 * This method is used to retrieve the date and the time from device
	 * @return The actual date
	 */
	private String getDate(){
		
		//The string that contains date
		String date;
		
		//A object to format the values of the date
		DecimalFormat decimalFormat = new DecimalFormat("00");
		
		Calendar cal = Calendar.getInstance();
		date = decimalFormat.format( cal.get(Calendar.DAY_OF_MONTH) ) + "/" +
			   decimalFormat.format( cal.get(Calendar.MONTH) + 1 ) + "/" +
			   decimalFormat.format( cal.get(Calendar.YEAR) ) + " " +
			   decimalFormat.format( cal.get(Calendar.HOUR_OF_DAY) ) + ":" +
			   decimalFormat.format( cal.get(Calendar.MINUTE) );	
		
		return date;	
	}
	
	
	/**
	 * @author Danilo Dessi'
	 * 
	 * This method computes the distance between 2 pairs of coordinates
	 * @param startLatitude The latitude of the first pair
	 * @param startLongitude The longitude of the first pair
	 * @param endLatitude The latitude of the second pair
	 * @param endLongitude The longitude of the second pair
	 * @return The distance in meters between pairs
	 */
	private double distanceBetweenCoordinates(double startLatitude, double startLongitude, double endLatitude, double endLongitude){
		
		//distance between 2 subsequent location
		float [] results = new float[1];//variable to save distance by function Location.distanceBetween
		Location.distanceBetween(startLatitude, startLongitude, endLatitude, endLongitude, results);//distance calculation 
		
		return results[0];		
	}
	
	
	/**
	 * @author Andreas Scalas e Danilo Dessi'
	 * This function is used to manage the service GPS updates. It create a thread to save new detection
	 * and to calculate the new values that are shown to user. 
	 */
	private AsyncTask<Void, Void, Void> managerGPSUpdate(){
		
		AsyncTask<Void, Void, Void> thread = new AsyncTask<Void, Void, Void>(){  
				
			private double speed; 		          //variable to save speed
			private double distance;	          //variable to save distance
			private double time;		          //variable to save time
			private double latitude;	          //variable to save latitude
			private double longitude;	          //variable to save longitude    	
			private double totalDistance;         //variable to save total distance
			private double meanSpeedLastXMeters;  //variable to save the mean speed in the last x meters
			private double meanPaceLastXMeters;   //variable to save the mean pace in the last x meters
			
        	@Override
            protected void onPreExecute() {
                super.onPreExecute();
                
                //initializzation distance
                distance = 0;
            }
        	
			@Override
			protected Void doInBackground(Void... arg0) {
				
				clientListener = new LocationLoggerService.OnNewGPSPointsListener() {
    				
    				@Override
    				public void onNewGPSPoint() {
    					
    					//update the state of GPS. GPS is sending updates...
    					stateGPS = mService.getStateConnection();
    					
    					//if the chronometer is running..
    					if(stateChronometer) {
    						
	    					// Retrieving GPS coordinates and speed
							latitude = mService.getLatitude();
							longitude = mService.getLongitude();
							speed = mService.getSpeed() * MULTIPLIER_FOR_KILOMETERS_HOURS;
								    						
							//Retrieval of the time from chronometer
							time = ((double) chronometer.getTimeElapsed()) / MILLISECOND_TO_SECOND;
	    					
	    					if( !training.isEmpty() ) {
	    						
	    						//Retrieving last detection
	    						TrainingElement last = training.lastDetection();
	    						//we calculate the total distance
	    						distance = last.getDistance() + distanceBetweenCoordinates(last.getLatitude(), last.getLongitude(), latitude, longitude);
	    								
	    						//save data of new location
	    						training.add(new TrainingElement(speed, distance, time, latitude, longitude));
	    						
	    					} else {
	    							
	    							//first detection saving
	    							training.add(new TrainingElement(speed, distance, time, latitude, longitude));
	    							
	    					}
	    					
	    					//we compute the new statistics that are shown to user
	    					totalDistance = training.totalDistance();
	    					meanSpeedLastXMeters = training.meanSpeedXMeters(setLastXMeters);
	    					meanPaceLastXMeters = training.meanPaceXMeters(setLastXMeters);
	    					
	    					//we notify to view of the new statistics
	    					publishProgress();
	    					
    					}
    					
    			    }
    				
    				
    			};
    			
    			//The listener is registered to receive updates
    			mService.addOnNewGPSPointsListener(clientListener);
    			
    			while(! isCancelled());
				return null;
			}
			
			@Override
			protected void onProgressUpdate(Void... values) {
				
				// Setting values on TextView component
				start.setClickable(true);
				defineArrowTransparency();
				start.setSpeed( format( speed ) + " " + getString(R.string.measurement_unit_speed) );
				stats.setText1( format( totalDistance / DIVISOR_FROM_METERS_TO_KILOMETERS ) + " " + getString(R.string.measurement_unit_distance) );
				stats.setText2( format( meanSpeedLastXMeters ) + " " + getString(R.string.measurement_unit_speed)  );
				stats.setText3( format( meanPaceLastXMeters ) + " " + getString(R.string.measurement_unit_pace)  );
				start.invalidate();
				stats.invalidate();
				
			}
			
			/**
			 * @author Andreas Scalas
			 * Method that defines the transparency of the speed arrow 
			 * proportionally to the speed
			 */
			private void defineArrowTransparency() {
				
				int intensity;
								
				if(speed > (setSpeed + setSpeed * ACCEPTANCE_RATIO)){
					
					/*If the speed is greater then the speed defined by the user
					 *(with a defined acceptance interval), the intensity is defined as:
					 */
					intensity=(int) ((speed - setSpeed) / setSpeed * MAX_ALPHA_INTENSITY);
					
					/*Reached the MAX_ALPHA_INTENSITY value, the intensity stop growing,
					 *even if the speed is greater.
					 */
					if(intensity > MAX_ALPHA_INTENSITY)
						intensity = MAX_ALPHA_INTENSITY;
					
					arrow.setAlphaIntensity(intensity);
					arrow.setTooFast(true);
					arrow.setTooSlow(false);
				
				}else if(speed < (setSpeed - setSpeed * ACCEPTANCE_RATIO)){
					
					/*If the speed is lower then the speed defined by the user
					 *(with a defined acceptance interval), the intensity is defined as:
					 */
					intensity = (int) ((setSpeed - speed) / setSpeed * MAX_ALPHA_INTENSITY);
					
					arrow.setAlphaIntensity(intensity);
					arrow.setTooFast(false);
					arrow.setTooSlow(true);
					
				} else {
					
					arrow.setAlphaIntensity(MAX_ALPHA_INTENSITY);
					arrow.setTooFast(false);
					arrow.setTooSlow(false);
					
				}
				
				//Forces the arrow to refresh
				arrow.invalidate();
				
			}
	
		};
		 
		return thread;
		
	}
	
	
	/**
	 * @author Danilo Dessi'
	 * 
	 * This function is used to format the values that are shown in the view.
	 * @param value a value that must be formatted
	 * @return a format string of the value
	 */
	public static String format (double value){
		
		DecimalFormat decimalFormat = new DecimalFormat("0.00");
		
		return decimalFormat.format( value );
		
	}
	
	
    
}
